import cheerio from 'cheerio'
import _ from 'lodash'

const oneArray = (array: any) => {
  if (!_.isArray(array)) {
    return false
  }
  if (_.size(array) !== 1) {
    return false
  }
  return true
}

const defaultRest = (data: any) => {
  if (!_.isPlainObject(data)) {
    return data
  }
  if (!(_.size(data) === 1)) {
    return data
  }
  if (!_.has(data, 'default')) {
    return data
  }
  return data.default
}

const compactData = (data: any) => {
  if (_.isEmpty(data)) {
    return null
  }
  if (oneArray(data)) {
    return defaultRest(_.head(data))
  }
  return data
}

const getElements = (context: any) => {
  const { $, selector } = context
  if (!selector) {
    return $('html')
  }
  return $(selector)
}

const parseRule = (rule: any) => {
  const dftFilter = (item: any) => item
  if (_.isString(rule)) {
    if (rule.includes('@')) {
      const [selector, attr] = rule.split('@')
      return [selector, attr, dftFilter]
    } else {
      return [rule, 'text', dftFilter]
    }
  }
  if (_.isArray(rule)) {
    const [selector, attr = 'text', filter = dftFilter] = rule
    return [selector, attr, filter]
  }
  return []
}

const transformRules = (rules: any) => {
  if (_.isObject(rules) && !_.isArray(rules)) {
    return _.mapValues(rules, (rule: any) => parseRule(rule))
  }
  return { default: parseRule(rules) }
}

const eachElements = (context: any, elem: any) => {
  const { $, settings } = context
  const rules = transformRules(settings)
  const item: any = {}
  _.forEach(rules, (rule: any, key: any) => {
    const [selector, attr, filter] = rule
    let values: any[] = []
    $(elem).find(selector).each((_: any, el: any) => {
      let value = null
      if (attr === 'text') {
        value = $(el).text()
      } else if (attr === 'html') {
        value = $(el).prop('innerHTML')
      } else {
        value = $(el).attr(attr)
      }
      value = filter ? filter(value) : value
      values.push(value) 
    })
    item[key] = compactData(values) 
  })
  return item
}

const compose = (context: any) => {
  const latest: any[] = []
  const elements = getElements(context)
  elements.each((_: any, elem: any) => {
    const item = eachElements(context, elem)
    if (item != null) {
      latest.push(item)
    }
  })
  return compactData(latest)
}

class HtmlQuery {
  html: any
  $: any
  selector: any
  settings: any
  dataPool: any
  constructor(html: string) {
    this.dataPool = []
    this.html = html
    this.#load(html)
  }
  #load(html: string = this.html) {
    this.$ = cheerio.load(html)
    this.selector = null
    this.settings = {}
  }
  rules(settings: any) {
    this.settings = settings
    return this
  }
  range(selector: string) {
    this.selector = selector
    return this
  }
  concat() {
    const result = compose(this)
    this.dataPool.push(result)
    this.#load()
    return this
  }
  build() {
    this.concat()
    return compactData(this.dataPool)
  }
}

export default HtmlQuery